#include "iuARPTable.hpp"
#include <cstdio>
#include "iuInterface.hpp"
#include "iuIPAddress.hpp"

/**
 * @brief Initialise la table ARP
 */
iuARPTable::iuARPTable()
{
   m_entries = NULL;
   m_last = NULL;
   m_cpt = 0;
}

/**
 * @brief Detruit la table ARP
 */
iuARPTable::~iuARPTable()
{
   iuARPEntry *current;

   while (m_entries)
   {
      current = m_entries;
      m_entries = m_entries->next;
      delete current;
   }
}

/**
 * @brief Ajoute une entree a la table ARP
 * @param newEntry l'entree a ajouter
 * @note L'entree passee sera automatiquement detruite par la table
 */
void iuARPTable::addEntry(iuARPEntry *newEntry)
{
   iuARPEntry *entry;

   if (!newEntry || !newEntry->interface)
      return;

   //Si l'entree existe deja, on la met au debut
   if ((entry = search(newEntry->ip, newEntry->interface)))
   {
      promoteEntry(entry);
      entry->mac = newEntry->mac;
      delete newEntry;
      return;
   }

   //Ajoute l'entree au debut de la table ARP
   newEntry->prev = NULL;
   newEntry->next = m_entries;
   if (m_entries)
      m_entries->prev = newEntry;
   else
      m_last = newEntry;
   m_entries = newEntry;
   m_cpt++;

   //Supprime la derniere entree si la table etait pleine
   if (m_cpt > MAX_ENTRIES)
   {
      entry = m_last;
      m_last->prev->next = NULL;
      m_last = m_last->prev;
      m_cpt--;
      delete entry;
   }
}

/**
 * @brief Recherche une ligne de la table ARP
 * @param ip l'adresse IP a rechercher
 * @param interface l'interface a rechercher
 * @return Renvoie la ligne de la table ARP correspondante, ou NULL si aucune ne correspond
 */
iuARPEntry *iuARPTable::search(const iuIPAddress &ip, const iuInterface *interface)
{
   for (iuARPEntry *p = m_entries; p; p = p->next)
   {
      if (p->ip == ip && p->interface == interface)
      {
         promoteEntry(p);
         return p;
      }
   }

   return NULL;
}

/**
 * @brief Supprime toutes les entrees du cache ARP qui referencent l'interface donnee
 * @param in l'interface a supprimer du cache ARP
 */
void iuARPTable::removeEntriesByInterface(const iuInterface *in)
{
   iuARPEntry *p, *next;

   if (!in)
      return;

   for (p = m_entries; p; p = next)
   {
      next = p->next;
      if (p->interface == in)
      {
         if (p->prev)
            p->prev->next = NULL;
         else
            m_entries = p->next;

         if (p->next)
            p->next->prev = NULL;
         else
            m_last = p->prev;

         delete p;
      }
   }
}

/**
 * @brief Affiche la table de routage
 */
void iuARPTable::print() const
{
   iuARPEntry *p;
   int cpt;

   printf("Interface     IP Address       MAC Address\n");

   for (p = m_entries; p; p = p->next)
   {
      //Affiche le nom de l'interface
      cpt = printf("eth%d", p->interface->getNumber());
      for (int i = cpt; i < 14; i++)
         printf(" ");

      //Affiche l'IP
      cpt = printf("%s", p->ip.toString());
      for (int i = cpt; i < 17; i++)
         printf(" ");

      //Affiche l'adresse MAC
      printf("%s\n", p->mac.toString());
   }
}

/**
 * @brief Deplace l'entree au debut de la table.
 * Elle sera ainsi la derniere a etre supprimer du cache ARP
 * @param entry l'entree a promouvoir
 */
void iuARPTable::promoteEntry(iuARPEntry *entry)
{
   if (!entry || !entry->prev)
      return;

   //Supprime la ligne de la table
   entry->prev->next = entry->next;
   if (entry->next)
      entry->next->prev = entry->prev;
   else
      m_last = entry->prev;

   //Ajoute l'entree au debut de la table
   entry->prev = NULL;
   entry->next = m_entries;
   m_entries->prev = entry;
   m_entries = entry;
}
